package com.mallcai.bigdata.ladon.service.system.impl;

import com.cake.bigdata.starter.exception.category.BusinessException;
import com.mallcai.bigdata.ladon.common.SessionThreadLocal;
import com.mallcai.bigdata.ladon.config.props.SystemProperties;
import com.mallcai.bigdata.ladon.dpl.entity.system.*;
import com.mallcai.bigdata.ladon.dpl.mapper.system.*;
import com.mallcai.bigdata.ladon.dpl.vo.CommonInfoType;
import com.mallcai.bigdata.ladon.service.report.IReportService;
import com.mallcai.bigdata.ladon.service.system.IAccountService;
import com.mallcai.bigdata.ladon.service.system.IMailService;
import com.mallcai.bigdata.ladon.service.system.IResService;
import com.mallcai.bigdata.ladon.service.system.IRoleService;
import com.mallcai.bigdata.ladon.tools.CodeBuilderTool;
import com.mallcai.bigdata.ladon.tools.MD5Util;
import com.mallcai.bigdata.ladon.utils.CollectionUtils;
import com.mallcai.bigdata.ladon.utils.MaskUtils;
import com.mallcai.bigdata.ladon.vo.AuthInfo;
import com.mallcai.bigdata.ladon.vo.CommonInfoVo;
import com.mallcai.bigdata.ladon.vo.UserVo;
import com.mallcai.bigdata.ladon.vo.system.MenuVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.util.Assert;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.cake.bigdata.starter.exception.error.CommonErrorCode.UNAUTHORIZED;

/**
 * Created by oneape<oneape15@163.com>
 * Created 2019-06-09 23:20.
 * Modify:
 */
@Slf4j
@Service
public class AccountService implements IAccountService {
    @Autowired
    private SystemProperties sysProps;

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private UserSessionMapper userSessionMapper;

    @Autowired
    private UserRlRoleMapper userRlRoleMapper;
    @Autowired
    private IMailService iMailService;
    @Autowired
    private IRoleService iRoleService;
    @Autowired
    private IResService iResService;
    @Autowired
    private IReportService iReportService;

    /**
     * 根据AppId获取所有用户列表
     *
     * @return List
     */
    @Override
    public List<User> getAllByAppId() {
        return userMapper.getAllByAppId();
    }

    /**
     * 根据username 查找用户信息
     *
     * @param username String 用户名
     * @return User
     */
    @Override
    public User findByUsername(String username) {
        return userMapper.getByUsername(username);
    }

    @Override
    public User getUserInfoByToken(String token) {
        Assert.isTrue(StringUtils.isNotBlank(token), "会话Token为空");
        // 根据token获取用户信息
        UserSession us = userSessionMapper.findByToken(token);

        // 验证token
        if (us == null) {
            throw new BusinessException(UNAUTHORIZED.getCode(), "无效的Token");
        }

        Long loginTime = us.getLoginTime();

        int timeout = us.getTimeout();
        if (timeout <= 0) {
            timeout = TOKEN_TIMEOUT; // 默认60分钟失效
        }
        if (loginTime == null || loginTime + timeout * ONE_MINUTE < System.currentTimeMillis()) {
            throw new BusinessException(UNAUTHORIZED.getCode(), "登录令牌已失效, 请重新登录");
        }

        return userMapper.getById(us.getUserId());
    }

    @Override
    public User getCurrentUser() {
        return SessionThreadLocal.getSession();
    }

    @Override
    public UserVo login(String username, String password) {
        User user = findByUsername(username);
        if (user == null || !StringUtils.equals(password, user.getPassword())) {
            throw new BusinessException("10002", "用户名或密码错误~");
        }

        // 生成登录成功token
        String token = createUserSessionToken(user.getId());
        if (StringUtils.isNotBlank(token)) {

            UserVo vo = new UserVo();
            BeanUtils.copyProperties(user, vo);
            vo.setToken(token);
            return vo;
        }
        return null;
    }

    /**
     * 创建用户会话Token
     *
     * @param userId Long 用户Id
     * @return String 会话token
     */
    private synchronized String createUserSessionToken(Long userId) {
        String randomStr = UUID.randomUUID().toString().replaceAll("-", "");

        String rawStr = randomStr + TOKEN_INFO_SPLIT + userId;
        String token = "";
        try {
            token = MD5Util.getMd5(rawStr);
        } catch (Exception e) {
            //
            log.error("生成token失败： {}", e.getMessage());
        }

        UserSession us = new UserSession(userId, token);
        us.setLoginTime(System.currentTimeMillis());
        us.setTimeout(120); // 二小时失效
        int status = userSessionMapper.insert(us);
        if (status > 0) {
            return token;
        } else {
            throw new BusinessException("10002", "登录繁忙，请稍后再试~");
        }
    }

    /**
     * 根据条件查询用户列表
     *
     * @param params Map
     * @return List
     */
    @Override
    public List<User> getByParams(Map<String, Object> params) {
        return userMapper.getByParams(params);
    }

    /**
     * 添加用户
     *
     * @param user User
     * @return int
     */
    @Transactional
    @Override
    public int addUser(User user, List<Long> roleIds) {
        Assert.isTrue(StringUtils.isNotBlank(user.getEmail()), "邮箱地址为空");
        Assert.isTrue(StringUtils.isNotBlank(user.getUsername()), "用户名为空");
        List<User> users = userMapper.getSameUsername(user.getUsername());
        if (users != null && users.size() > 0) {
            throw new BusinessException("用户名已存在");
        }
        int existEmail = userMapper.isExistEmail(user.getEmail(), null);
        if (existEmail > 0) {
            throw new BusinessException("邮箱地址【" + user.getEmail() + "】已被占用, 不能重复使用~");
        }
        String rawPwd = user.getPassword();
        if (StringUtils.isBlank(rawPwd)) {
            rawPwd = CodeBuilderTool.randomStr(6); // 随机生成一个密码
        }
        String pwdMd5 = MD5Util.ladonUserPassword(user.getUsername(), rawPwd, sysProps.getSalt());
        user.setPassword(pwdMd5);
        user.setStatus(1);
        int status = userMapper.insert(user);
        if (status > 0) {
            if (roleIds != null && roleIds.size() > 0) {
                roleIds.forEach(rId -> userRlRoleMapper.insert(new UserRlRole(user.getId(), rId)));
            }
            try {
                String templateName = "email/templates/sys-reg-success.html";
                String content = getFileContent(templateName);

                // 这里不使用MessageFormat.format的原因,在于 style中会存在{dispaly:none}这种字符串存在
                content = StringUtils.replace(content, "{0}", user.getNickname());
                content = StringUtils.replace(content, "{1}", sysProps.getLoginUri());
                content = StringUtils.replace(content, "{2}", user.getUsername());
                content = StringUtils.replace(content, "{3}", rawPwd);

                iMailService.sendSimpleMail(user.getEmail(), "拉冬-数据平台密码重置", content);
            } catch (Exception e) {
                log.error("发送邮件失败~", e);
                throw new BusinessException("发送邮件失败, 邮箱地址:" + user.getEmail());
            }
        }
        return status;
    }

    /**
     * 修改用户信息
     *
     * @param user User
     * @return int
     */
    @Transactional
    @Override
    public int updateUser(User user, List<Long> roleIds) {
        Assert.isTrue(StringUtils.isNotBlank(user.getEmail()), "邮箱地址为空");
        Assert.isTrue(user.getId() != null, "主键为空");
        Assert.isTrue(StringUtils.isNotBlank(user.getUsername()), "用户名为空");
        List<User> users = userMapper.getSameUsername(user.getUsername());
        if (users != null && users.size() > 0) {
            for (User u : users) {
                if (!u.getId().equals(user.getId())) {
                    throw new BusinessException("用户名已存在");
                }
            }
        }
        int existEmail = userMapper.isExistEmail(user.getEmail(), user.getId());
        if (existEmail > 0) {
            throw new BusinessException("邮箱地址【" + user.getEmail() + "】已被占用, 不能重复使用~");
        }

        boolean resetPwd = false;
        String rawPwd = user.getPassword();
        if (StringUtils.isNotBlank(rawPwd)) {
            String pwdMd5 = MD5Util.ladonUserPassword(user.getUsername(), user.getPassword(), sysProps.getSalt());
            user.setPassword(pwdMd5);
            resetPwd = true;
        }
        int status = userMapper.update(user);
        if (status > 0) {
            // 重新插入用户角色
            userRlRoleMapper.deleteByUserId(user.getId());
            if (roleIds != null && roleIds.size() > 0) {
                roleIds.forEach(r -> userRlRoleMapper.insert(new UserRlRole(user.getId(), r)));
            }

            // 密码被修改了,必须发送一封邮件给相应的用户
            if (resetPwd) {
                sendEmailByResetPwd(user, rawPwd);
            }
        }
        return status;
    }

    /**
     * 删除用户信息
     *
     * @param user User
     * @return int
     */
    @Override
    public int deleteUser(User user) {
        return userMapper.delete(user);
    }

    /**
     * 重置用户密码
     *
     * @param userId Long
     * @return int
     */
    @Override
    public int resetPwd(Long userId) {
        Assert.isTrue(userId != null, "用户Id为空");
        User user = userMapper.getById(userId);
        if (user == null) {
            throw new BusinessException("用户不存在");
        }

        String pwd = CodeBuilderTool.randomStr(6); // 随机生成一个密码
        String pwdMd5 = MD5Util.ladonUserPassword(user.getUsername(), pwd, sysProps.getSalt());
        user.setPassword(pwdMd5);

        int status = userMapper.update(user);
        if (status > 0) {
            // 密码被修改了,必须发送一封邮件给相应的用户
            sendEmailByResetPwd(user, pwd);
        }
        return status;
    }

    /**
     * 修改密码
     *
     * @param oldPwd String
     * @param newPwd String
     * @return int
     */
    @Override
    public int changePwd(String oldPwd, String newPwd) {
        User user = getCurrentUser();
        if (user == null) {
            throw new BusinessException("请重新登录");
        }
        String oldPwdMd5 = MD5Util.ladonUserPassword(user.getUsername(), oldPwd, sysProps.getSalt());
        if (!StringUtils.equals(oldPwdMd5, user.getPassword())) {
            throw new BusinessException("旧密码不正确");
        }

        User nUser = new User();
        nUser.setId(user.getId());
        String pwdMd5 = MD5Util.ladonUserPassword(user.getUsername(), newPwd, sysProps.getSalt());
        nUser.setPassword(pwdMd5);
        return userMapper.update(nUser);
    }

    private void sendEmailByResetPwd(User user, String rawPwd) {
        try {
            String templateName = "email/templates/user-reset-pwd.html";
            String content = getFileContent(templateName);

            // 这里不使用MessageFormat.format的原因,在于 style中会存在{dispaly:none}这种字符串存在
            content = StringUtils.replace(content, "{0}", user.getUsername());
            content = StringUtils.replace(content, "{1}", sysProps.getLoginUri());
            content = StringUtils.replace(content, "{2}", rawPwd);

            iMailService.sendSimpleMail(user.getEmail(), "拉冬-数据平台密码重置", content);
        } catch (Exception e) {
            log.error("发送邮件失败~", e);
            throw new BusinessException("发送邮件失败, 邮箱地址:" + user.getEmail());
        }
    }

    /**
     * 获取当前用户的前端菜单列表
     *
     * @return List
     */
    @Override
    public List<MenuVo> getCurrentMenus() {
        List<MenuVo> menus = new ArrayList<>();
        User user = getCurrentUser();
        if (user == null) {
            return menus;
        }
        List<Long> roleIds = user.getRoleIds();

        List<Resource> resources = iResService.getAllResource();

        // 拉冬之父判断

        boolean isFather = sysProps.getPhorcys().contains(user.getUsername());
        Map<Long, List<Integer>> masks = new HashMap<>();
        if (!isFather) {
            masks = iRoleService.getRoleRes(roleIds);
        }

        // 根据权限生成菜单列表
        Map<Long, MenuVo> map = new LinkedHashMap<>();
        for (Resource r : resources) {
            if (r.getType() != 0) {
                continue; // 不是菜单, 不返回
            }
            Long pId = r.getParentId();
            MenuVo menuVo = new MenuVo(r.getPath(), r.getName(), r.getIcon());
            // 父节点
            if (pId == null || pId == 0) {
                map.put(r.getId(), menuVo);
                continue;
            }

            // 子节点判断是否有权限
            if (!isFather && !masks.containsKey(r.getId())) {
                continue;
            }

            // 这里断定, 遍历到子节点时,父节点已经添加到Map中了
            // 如果父节点还未在,说明是一个bug(查询时,根据parent_id进行了order by操作)
            if (map.containsKey(pId)) {
                if (map.get(pId).getChildren() == null) {
                    map.get(pId).setChildren(new ArrayList<>());
                }
                map.get(pId).getChildren().add(menuVo);
            }
        }
        // 过滤掉没有子节点的菜单
        List<MenuVo> ret = map.values()
                .stream()
                .filter(menuVo -> menuVo.getChildren() != null && menuVo.getChildren().size() > 0)
                .collect(Collectors.toList());

        // 获取报表权限
        if (isFather) {
            List<Role> allRoles = iRoleService.getAllRoles();
            if (allRoles != null) {
                allRoles.forEach(r -> roleIds.add(r.getId()));
            }
        }
        List<MenuVo> reportMenus = iReportService.getReportTreeOfMenu(user.getId(), roleIds);
        if (reportMenus != null && reportMenus.size() > 0) {
            MenuVo reportRoot = new MenuVo("/query", "报表查询", "eye");
            reportRoot.setChildren(reportMenus);
            ret.add(reportRoot);
        }

        return new ArrayList<>(ret);
    }

    /**
     * 当前用户是否有写权限,无权限时抛出业务异常
     *
     * @param userId 负责人用户id
     */
    @Override
    public void checkPermission(Long userId) {
        User user = getCurrentUser();
        // 拉冬之父判断
        boolean isFather = sysProps.getPhorcys().contains(user.getUsername());
        if (!isFather && !user.getId().equals(userId)) {
            throw new BusinessException("您只能操作自己的报表");
        }
    }

    /**
     * 注册用户
     *
     * @param user User 用户信息
     * @return int
     */
    @Transactional
    @Override
    public int regUser(User user) {
        Assert.isTrue(StringUtils.isNotBlank(user.getUsername()), "用户名为空");
        Assert.isTrue(StringUtils.isNotBlank(user.getPassword()), "用户密码为空");

        int existEmail = userMapper.isExistEmail(user.getEmail(), null);
        if (existEmail > 0) {
            throw new BusinessException("邮箱地址【" + user.getEmail() + "】已被占用, 不能重复使用~");
        }

        User tmp = findByUsername(user.getUsername());
        if (tmp != null) {
            throw new BusinessException("用户已存在, 尝试登录试试吧");
        }

        String emailCode = UUID.randomUUID().toString().replaceAll("-", ""); // 生成激活码
        user.setStatus(0); //  注册未激活
        user.setEmailActiveCode(emailCode);
        user.setCreatedBy(1L); // 系统注册
        int status = userMapper.insert(user);
        if (status > 0) {
            try {
                String templateName = "email/templates/user-reg-success.html";
                String content = getFileContent(templateName);

                String uri = sysProps.getUri() + "/activate/" + emailCode;
                // 这里不使用MessageFormat.format的原因,在于 style中会存在{dispaly:none}这种字符串存在
                content = StringUtils.replace(content, "{0}", user.getUsername());
                content = StringUtils.replace(content, "{1}", uri);

                iMailService.sendSimpleMail(user.getEmail(), "拉冬-数据平台用户注册", content);
            } catch (Exception e) {
                log.error("发送邮件失败~", e);
                throw new BusinessException("发送邮件失败, 邮箱地址:" + user.getEmail());
            }
        }
        return status;
    }

    private String getFileContent(String templateName) throws Exception {
        InputStream is = ClassLoader.getSystemResourceAsStream(templateName);
        BufferedReader fileReader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();
        String line;
        try {
            while ((line = fileReader.readLine()) != null) {
                sb.append(line);
            }
        } finally {
            is.close();
            fileReader.close();
        }

        return sb.toString();
    }

    /**
     * 根据邮箱code,激活用户
     *
     * @param emailCode String
     * @return User
     */
    @Transactional
    @Override
    public User activateUser(String emailCode) {
        if (StringUtils.isBlank(emailCode)) {
            throw new BusinessException("邮箱激活码为空");
        }

        User user = userMapper.getByEmailCode(emailCode);
        if (user == null) {
            throw new BusinessException("无效的邮箱激活码");
        }
        if (user.getStatus() == 0) {
            userMapper.activateUser(user.getId()); // 激活邮箱
        }
        return user;
    }

    /**
     * 判断是否拥有指定角色
     *
     * @param userId   Long   用户id
     * @param roleCode String 角色code
     * @return boolean true - 存在; false - 不存在
     */
    @Override
    public boolean hasRole(Long userId, String roleCode) {
        if (StringUtils.isBlank(roleCode) || userId == null) {
            return false;
        }

        int size = userRlRoleMapper.hasRole(userId, roleCode);
        return size > 0;
    }


    /**
     * 获取用户资源操作权限
     *
     * @param userId Long
     * @return Map
     */
    @Override
    public Map<String, List<Integer>> getResOptPermission(Long userId) {
        Map<String, List<Integer>> map = new HashMap<>();
        User user = userMapper.getById(userId);
        if (user == null) {
            return map;
        }

        if (sysProps.getPhorcys().contains(user.getUsername())) {
            // 拉冬之父,拥有全部角色
            List<Resource> resources = iResService.getAllResource();

            List<Integer> masks = MaskUtils.getAllList();
            resources.forEach(resource -> {
                if (StringUtils.isNotBlank(resource.getPath())) {
                    map.put(resource.getPath(), masks);
                }
            });
        } else {
            List<RoleRlResource> roleRlResources = iResService.getOptPermissionByUserId(userId);
            if (roleRlResources != null && roleRlResources.size() > 0) {
                roleRlResources.forEach(rr -> {
                    List<Integer> masks = MaskUtils.getList(rr.getMask());
                    map.put(rr.getPath(), masks);
                });
            }
        }
        return map;
    }

    /**
     * 获取用户的权限信息
     *
     * @param userId Long
     * @return AuthInfo
     */
    @Override
    public AuthInfo getUserAuthInfo(Long userId) {
        AuthInfo authInfo = new AuthInfo();

        // 获取用户拥有的权限列表
        List<String> authCodes = iResService.getAuthCodeByUserId(userId);
        authInfo.setAuthCodes(authCodes);

        return authInfo;
    }

    @Override
    public boolean isSuperMan() {
        User user = SessionThreadLocal.getSession();
        if (user == null || StringUtils.isBlank(user.getUsername())) {
            return false;
        }
        return sysProps.getPhorcys().stream().anyMatch(name -> name.trim().equalsIgnoreCase(user.getUsername()));
    }

    /**
     * 删除数据库中过期token
     */
    @Override
    public void deleteExpireToken() {
        userSessionMapper.deleteExpireToken();
    }
}
